﻿// Copyright (c) MichaConrad. All rights reserved. See License.txt in the project root for license information.

namespace LinqToLdapExtensions.Json
{
    using System;
    using System.Collections.Generic;
    using LinqToLdap.Collections;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    /// <summary>Converts a <see cref="DirectoryAttributes"/> to and from Json.
    /// <para>DirectoryAttributes is more or less a <see cref="System.Collections.Generic.KeyValuePair{TKey, TValue}"/>type.
    /// This converter flattens the key values to a "real" Json object where key is property and value is the value of the 
    /// property.</para>
    /// <b>!Experimental!</b>
    /// <para>The reader part might not really work. I didn't find a way to restore the DirectoryAttributes collection because
    /// there is no public way to instantiate it. Intead it now creates a list of objects where all properties are in replace state...
    /// </para>
    /// </summary>
    public class DirectoryAttributesConverter : JsonConverter
    {
        /// <summary>
        /// Writes the JSON representation of the object.
        /// </summary>
        /// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
        /// <param name="value">The value.</param>
        /// <param name="serializer">The calling serializer.</param>
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var list = value as List<IDirectoryAttributes>;

            if (list == null)
            {
                return;
            }

            writer.WriteStartArray();

            foreach (var attributes in list)
            {
                // starting entry
                writer.WriteStartObject();

                writer.WritePropertyName("distinguishedName");
                writer.WriteValue(attributes.DistinguishedName);

                writer.WritePropertyName("attributeNames");
                serializer.Serialize(writer, attributes.AttributeNames);

                foreach (var val in attributes)
                {
                    writer.WritePropertyName(val.Key);
                    serializer.Serialize(writer, val.Value);
                }

                writer.WriteEndObject();
            }

            writer.WriteEndArray();
        }

        /// <summary>
        /// Reads the JSON representation of the object.
        /// TODO: maybe better to simply return the dynamic result instead of trying to deserialize into an IDirectoryAttributes modified values...
        /// </summary>
        /// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
        /// <param name="objectType">Type of the object.</param>
        /// <param name="existingValue">The existing value of object being read.</param>
        /// <param name="serializer">The calling serializer.</param>
        /// <returns>The object value.</returns>
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var root = serializer.Deserialize<IEnumerable<dynamic>>(reader);

            var result = new List<IDirectoryAttributes>();

            foreach (var attribute in root)
            {
                var jsonObj = attribute as JObject;
                if (jsonObj == null)
                {
                    continue;
                }

                var disName = attribute.distinguishedName;

                var keyValues = new List<KeyValuePair<string, object>>();

                foreach (var name in attribute.attributeNames)
                {
                    JToken token;
                    jsonObj.TryGetValue(name.ToString(), StringComparison.OrdinalIgnoreCase, out token);

                    switch (token.Type)
                    {
                        case JTokenType.Array:
                        case JTokenType.Bytes:
                            keyValues.Add(new KeyValuePair<string, object>(name.ToString(), JsonConvert.DeserializeObject<string[]>(token.ToString())));
                            break;
                        case JTokenType.Float: // not sure if needed or if it is working
                            keyValues.Add(new KeyValuePair<string, object>(name.ToString(), double.Parse(token.ToString())));
                            break;
                        case JTokenType.Integer: // not sure if needed or if it is working
                            keyValues.Add(new KeyValuePair<string, object>(name.ToString(), int.Parse(token.ToString())));
                            break;
                        case JTokenType.Boolean:
                        case JTokenType.Comment:
                        case JTokenType.String:
                            keyValues.Add(new KeyValuePair<string, object>(name.ToString(), token.ToString()));
                            break;
                        case JTokenType.TimeSpan:
                        case JTokenType.Object:
                        case JTokenType.Uri:
                        case JTokenType.Date:
                        case JTokenType.Guid:
                        case JTokenType.Property: // not sure if needed or if it is working
                            keyValues.Add(new KeyValuePair<string, object>(name.ToString(), JsonConvert.DeserializeObject<string>(token.ToString())));
                            break;
                    }
                }
                
                result.Add(new DirectoryAttributes(disName.ToString(), keyValues));
            }

            return result;
        }

        /// <summary>
        /// Determines whether this instance can convert the specified object type.
        /// </summary>
        /// <param name="objectType">Type of the object.</param>
        /// <returns>
        ///         <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
        /// </returns>
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(List<IDirectoryAttributes>);
        }
    }
}
